/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_surf.c: surface-related refresh code

#include "quakedef.h"

ID3D10Buffer *d3d_SurfaceVertexes = NULL;

QSHADER d3d_UnlitSurfaces;
QSHADER d3d_LightmappedShader[4];
QSHADER d3d_SurfZFill;
QSHADER d3d_SurfDeferred;

class CSurfHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		d3d_UnlitSurfaces.CreateShaders (IDR_SURFSHADER, "SurfUnlitVS", NULL, "SurfUnlitPS", INPUT_LAYOUT_SURF);

		d3d_LightmappedShader[0].CreateShaders (IDR_SURFSHADER, "SurfLightmappedVS", NULL, "SurfLightmapped0PS", INPUT_LAYOUT_SURF);
		d3d_LightmappedShader[1].CreateShaders (IDR_SURFSHADER, "SurfLightmappedVS", NULL, "SurfLightmapped1PS", INPUT_LAYOUT_SURF);
		d3d_LightmappedShader[2].CreateShaders (IDR_SURFSHADER, "SurfLightmappedVS", NULL, "SurfLightmapped2PS", INPUT_LAYOUT_SURF);
		d3d_LightmappedShader[3].CreateShaders (IDR_SURFSHADER, "SurfLightmappedVS", NULL, "SurfLightmapped3PS", INPUT_LAYOUT_SURF);

		d3d_SurfZFill.CreateShaders (IDR_SURFSHADER, "SurfZFillVS", NULL, NULL, INPUT_LAYOUT_SURF);
		d3d_SurfDeferred.CreateShaders (IDR_LIGHT, "SurfDeferredVS", "SurfDeferredGS", "SurfDeferredPS", INPUT_LAYOUT_SURF);
	}

	void OnReleaseDevice (void)
	{
		SAFE_RELEASE (d3d_SurfaceVertexes);
	}

	void OnModeChange (void)
	{
		// bind surfaces to slot 6 always so that it doesn't need to be checked and never needs to change
		UINT stride = sizeof (brushpolyvert_t);
		UINT offset = 0;

		d3d_Device->IASetVertexBuffers (BUFFER_SLOT_SURFACES, 1, &d3d_SurfaceVertexes, &stride, &offset);
	}
} d3d_SurfHandler;


/*
===============
R_TextureAnimation

Returns the proper texture for a given time and base texture
===============
*/
texture_t *R_TextureAnimation (texture_t *base, int frame)
{
	int		reletive;
	int		count;
	texture_t *original = base;

	if (frame)
	{
		if (base->alternate_anims)
			base = base->alternate_anims;
	}

	if (!base->anim_total)
		return base;

	reletive = (int) (cl.time * 10) % base->anim_total;
	count = 0;

	while (base->anim_min > reletive || base->anim_max <= reletive)
	{
		base = base->anim_next;

		// just return the original texture on error
		if (!base) return original;
		if (++count > 100) return original;
	}

	return base;
}


/*
=============================================================

	BRUSH MODELS

=============================================================
*/

void R_DrawGenericSurfaces (model_t *m, chaintype_t Type, int texnum, QSHADER *shader, ID3D10BlendState *blendmode, ID3D10DepthStencilState *depthmode)
{
	d3d_State->OMSetDepthState (depthmode);
	d3d_State->OMSetBlendState (blendmode);

	// set the shaders we're going to use
	shader->Bind ();

	// and now we draw
	for (int i = 0; i < m->numtextures; i++)
	{
		// get this texture
		texture_t *tex = m->textures[i];

		// e2m3 crap
		if (!tex) continue;
		if (!tex->Animated[texnum]) continue;

		// check for something to draw
		if (!tex->TextureChain[Type].TextureChain) continue;

		// surface texture always goes in slot 9 so that it won't be overwritten
		d3d_Device->PSSetShaderResources (TEXTURE_SLOT_SURFACES, 1, &tex->Animated[texnum]->SRV);

		R_DrawNewTextureChain (tex, Type);
	}
}


void R_DrawZFillSurfaces (texturechain_t *Chain)
{
	// fill the depth buffer
	d3d_State->OMSetBlendState (d3d_BlendZFill);
	d3d_State->OMSetDepthState (d3d_ZmodeNormal);

	d3d_SurfZFill.Bind ();

	// and now draw it all
	R_DrawNewTextureChain (Chain, CHAIN_ZFILL);
}


void R_DrawLightmappedSurfaces (model_t *m, chaintype_t Type, int style, ID3D10BlendState *blendmode, ID3D10DepthStencilState *depthmode)
{
	R_DrawGenericSurfaces (m, Type, 0, &d3d_LightmappedShader[style], blendmode, depthmode);
}


void R_DrawUnlitSurfaces (model_t *m, int texnum, chaintype_t Type, ID3D10BlendState *blendmode, ID3D10DepthStencilState *depthmode)
{
	R_DrawGenericSurfaces (m, Type, texnum, &d3d_UnlitSurfaces, blendmode, depthmode);
}


void R_DrawDeferredSurfaces (model_t *m)
{
	R_DrawGenericSurfaces (m, CHAIN_DEFERRED, 0, &d3d_SurfDeferred, d3d_BlendNone, d3d_ZmodeNormal);
}


void dlight_t::DrawBrush (entity_t *ent, model_t *m, QSHADER *shader)
{
	// setup state for drawing
	R_SetupSurfaceDrawState (ent, m);
	R_DrawGenericSurfaces (m, CHAIN_DYNAMICLIGHT, 0, shader, d3d_BlendAdditive, d3d_ZmodeNowrite);
}


void R_SetupSurfaceDrawState (entity_t *e, model_t *m, float alpha)
{
	if (e->PassNum != ref.BrushPasses)
	{
		extern ID3D10Buffer *d3d_SurfIndexes;

		// common vertex input setup (the vertex buffer was already bound)
		d3d_Device->IASetPrimitiveTopology (D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		d3d_Device->IASetIndexBuffer (d3d_SurfIndexes, DXGI_FORMAT_R32_UINT, 0);

		R_SetupObjectConstants (e, alpha);

		// move the eye position back to the entity's frame of reference
		Fog_SetEyePosition (e->localmatrix);

		// run texture animation
		R_AnimateTextures (m, e->frame);

		e->PassNum = ref.BrushPasses;
	}
}


void R_SetSolidAlphaState (msurface_t *surf)
{
	/*
	texture_t *t = surf->texinfo->texture;

	d3d_State->VSSetLayout (d3d_SurfUnlitVD);
	d3d_State->VSSetShader (d3d_SurfUnlitVS);
	d3d_State->PSSetShader (d3d_SurfUnlitPS);

	if (r_lightmap->value)
		d3d_State->PSSetTexture (0, d3d_WhiteTexture);
	else d3d_State->PSSetTexture (0, t->Animated[0]);
	*/
}


BOOL R_SetupBrushModel (entity_t *e)
{
	model_t *clmodel = e->model;

	if (!(e->effects & EF_STATICENT))
	{
		// get the transform in local space first so that we can correctly handle dlights and other stuff that depends on it
		// (test this with hipnotic rotating bmodels)
		if (e->angles[0] || e->angles[1] || e->angles[2])
		{
			e->localmatrix.Identity ();
			e->localmatrix.Translate (e->origin[0], e->origin[1], e->origin[2]);
			e->localmatrix.Rotate (e->angles[0], e->angles[1], e->angles[2]);
		}
		else e->localmatrix.Load (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, e->origin[0], e->origin[1], e->origin[2], 1);

		// compute the bmodel bbox
		Mod_ComputeBBox (&e->localmatrix, clmodel->mins, clmodel->maxs, e->bbox.mins, e->bbox.maxs);
	}

	// and now we can do the cull
	if (R_CullBox (ref.Frustum, e->bbox.mins, e->bbox.maxs, 15))
		return FALSE;

	// move vieworigin to local space for backface culling
	e->localmatrix.InverseTransform (e->modelorg, ref.ViewOrigin);

	// the entity is good to draw now
	return TRUE;
}


void R_SetupStaticBrushModel (entity_t *e)
{
	// just run a regular setup to pre-load it's local matrix and bbox
	R_SetupBrushModel (e);

	// set last so that it will skip over the building at runtime but not at load time
	e->effects |= EF_STATICENT;
}


/*
=================
R_DrawBrushModel
=================
*/
void R_DrawBrushModel (entity_t *e)
{
	model_t *clmodel = e->model;

	// mark the nodes as visible (needed for lighting)
	R_MarkNodes (e, clmodel->nodes + clmodel->hulls[0].firstclipnode, clmodel->surfaces);

	// chains belonging to the model should also be cleared here too
	R_ClearTextureChain (&clmodel->SkyChain);
	R_ClearTextureChain (&clmodel->ZFillChain);

	// add these surfaces to the draw lists
	msurface_t *surf = &clmodel->surfaces[clmodel->firstmodelsurface];
	float entalpha = ENTALPHA_DECODE (e->alpha);

	for (int i = 0; i < clmodel->nummodelsurfaces; i++, surf++)
	{
		if (surf->visframe != ref.FrameCount) continue;

		R_AddSurface (surf, surf->texinfo->texture, e, entalpha);
		//R_ChainSurface (surf, e, entalpha);
	}

	// and draw them
	R_NewDrawTextureChains (e);
	ref.BrushPasses++;
}


/*
=============================================================

	WORLD MODEL

=============================================================
*/

/*
================
R_RecursiveWorldNode
================
*/
void R_RecursiveWorldNode (mnode_t *node, int clipflags)
{
	if (!R_ClipWorldNode (node, clipflags)) return;

	// if a leaf node, draw stuff
	if (node->contents < 0)
	{
		R_HandleWorldLeaf ((mleaf_t *) node);
		return;
	}

	// node is just a decision point, so go down the apropriate sides
	// find which side of the node we are on
	mplane_t *plane = node->plane;
	float dot = Vector3Dot (r_worldentity.modelorg, plane->normal) - plane->dist;

	// recurse down the children, front side first
	R_RecursiveWorldNode (node->children[(dot >= 0) ? 0 : 1], clipflags);

	// draw stuff
	msurface_t *surf = cl.worldmodel->surfaces + node->firstsurface;

	for (int c = 0; c < node->numsurfaces; c++, surf++)
	{
		if (R_CullWorldSurface (surf, clipflags, dot)) continue;

		R_AddSurface (surf, surf->texinfo->texture, &r_worldentity, 1.0f);
	}

	// recurse down the back side
	R_RecursiveWorldNode (node->children[(dot >= 0) ? 1 : 0], clipflags);
}


/*
=============
R_DrawWorld
=============
*/
void R_DrawWorld (void)
{
	memset (&r_worldentity, 0, sizeof (entity_t));
	r_worldentity.model = cl.worldmodel;

	// local space is identity and world space is MVP
	r_worldentity.localmatrix.Identity ();
	r_worldentity.entitymatrix.Load (ref.MVPMatrix);

	Vector3Copy (r_worldentity.bbox.mins, cl.worldmodel->mins);
	Vector3Copy (r_worldentity.bbox.maxs, cl.worldmodel->maxs);
	Vector3Copy (r_worldentity.modelorg, ref.ViewOrigin);

	// chains belonging to the model should also be cleared here too
	R_ClearTextureChain (&cl.worldmodel->SkyChain);
	R_ClearTextureChain (&cl.worldmodel->ZFillChain);

	// add the world to the draw lists
	R_RecursiveWorldNode (cl.worldmodel->nodes, 15);

	// and draw it all
	R_NewDrawTextureChains (&r_worldentity);
}


void R_AddLeafPVS (mleaf_t *viewleaf)
{
	byte *vis = NULL;

	if (r_novis->boolean)
	{
		// ensuring that we have space for it
		QSCRATCHLOCKER Lock ((void **) &vis, __FUNCTION__);
		memset (vis, 0xff, (cl.worldmodel->numleafs + 7) >> 3);
	}
	else vis = Mod_LeafPVS (viewleaf, cl.worldmodel);

	// don't overflow the leafs array
	for (int i = 0, j = 1; i < cl.worldmodel->numleafs; i++, j++)
	{
		if (vis[i >> 3] & (1 << (i & 7)))
		{
			mnode_t *node = (mnode_t *) &cl.worldmodel->leafs[j];

			do
			{
				if (node->visframe == ref.VisFrameCount)
					break;

				node->visframe = ref.VisFrameCount;
				node = node->parent;
			} while (node);
		}
	}
}


/*
===============
R_MarkLeaves
===============
*/
void R_MarkLeaves (void)
{
	if (ref.ViewLeaf[1] == ref.ViewLeaf[0] || r_lockpvs->value) return;

	ref.VisFrameCount++;

	// clear the water texture if we're not underwater, otherwise bring it up to date
	if (!Mod_LeafIsUnderwater (ref.ViewLeaf[0]))
		ref.WaterTexture = NULL;
	else if (ref.ViewLeaf[0]->watertexture)
		ref.WaterTexture = ref.ViewLeaf[0]->watertexture;

	R_AddLeafPVS (ref.ViewLeaf[0]);

	// add up to 2 old view leafs to help with water transitions
	if (ref.ViewLeaf[1] && ref.ViewLeaf[0]->contents != ref.ViewLeaf[1]->contents) R_AddLeafPVS (ref.ViewLeaf[1]);
	if (ref.ViewLeaf[2] && ref.ViewLeaf[0]->contents != ref.ViewLeaf[2]->contents) R_AddLeafPVS (ref.ViewLeaf[2]);

	// shuffle viewleafs
	ref.ViewLeaf[2] = ref.ViewLeaf[1];
	ref.ViewLeaf[1] = ref.ViewLeaf[0];
}


/*
=============================================================================

SURFACE BUILDING

=============================================================================
*/

/*
================
GL_BuildPolygonForSurface
================
*/
void GL_BuildPolygonForSurface (model_t *m, msurface_t *surf, brushpolyvert_t *verts)
{
	byte styles[4] = {MAX_LIGHTSTYLES - 1, MAX_LIGHTSTYLES - 1, MAX_LIGHTSTYLES - 1, MAX_LIGHTSTYLES - 1};

	for (int i = 0; i < MAX_SURFACESTYLES && surf->styles[i] != 255; i++)
	{
		// this is safe cos MAX_LIGHTSTYLES is 64, sv.lightstyles is 64, cl_lightstyle is 64 and svc_lightstyle is a Sys_Error if >= 64
		// so despite the fact that d_lightstylevalue was originally sized at 256, it will in practice actually never go above 64!
		if (surf->styles[i] > MAX_LIGHTSTYLES - 1)
		{
			Con_Printf (PRINT_DEVELOPER, "surf->styles[%i] is %i\n", i, surf->styles[i]);
			styles[i] = MAX_LIGHTSTYLES - 1;
		}
		else styles[i] = surf->styles[i];
	}

	// reconstruct the polygon
	for (int i = 0; i < surf->numedges; i++)
	{
		int lindex = m->surfedges[surf->firstedge + i];
		float *vec = (lindex > 0) ? m->vertexes[m->edges[lindex].v[0]].position : m->vertexes[m->edges[-lindex].v[1]].position;
		float dot[2] = {Vector3Dot (vec, surf->texinfo->vecs[0]), Vector3Dot (vec, surf->texinfo->vecs[1])};

		// copy off position
		Vector3Copy (verts[i].xyz, vec);

		// eval texcoords based on surface type
		if (surf->flags & SURF_DRAWTURB)
		{
			// precalc as much of this as possible so that we don't need a more complex FS
			verts[i].st[0] = dot[0] * 0.015625f;
			verts[i].st[1] = dot[1] * 0.015625f;

			verts[i].lm[0] = dot[1] * M_PI / 64.0f;
			verts[i].lm[1] = dot[0] * M_PI / 64.0f;
		}
		else if (!(surf->flags & SURF_DRAWSKY))
		{
			// diffuse texture coordinates
			verts[i].st[0] = (dot[0] + surf->texinfo->vecs[0][3]) / surf->texinfo->texture->width;
			verts[i].st[1] = (dot[1] + surf->texinfo->vecs[1][3]) / surf->texinfo->texture->height;

			// lightmap texture coordinates
			verts[i].lm[0] = (dot[0] + surf->texinfo->vecs[0][3] - surf->texturemins[0] + (surf->light_s << 4) + 8.0f) / (LIGHTMAP_SIZE << 4);
			verts[i].lm[1] = (dot[1] + surf->texinfo->vecs[1][3] - surf->texturemins[1] + (surf->light_t << 4) + 8.0f) / (LIGHTMAP_SIZE << 4);
		}

		// output array slice index
		verts[i].mapnum = surf->lightmaptexturenum;

		// copy over the styles
		verts[i].styles[0] = styles[0];
		verts[i].styles[1] = styles[1];
		verts[i].styles[2] = styles[2];
		verts[i].styles[3] = styles[3];
	}
}


/*
==================
GL_BuildBrushSurfaces

Builds the lightmap texture
with all the surfaces from all brush models
==================
*/
int R_SortSurfacesByOrder (msurface_t *s1, msurface_t *s2)
{
	return s1->order - s2->order;
}


int R_SortSurfacesByTexture (msurface_t *s1, msurface_t *s2)
{
	if (s1->texinfo->texture == s2->texinfo->texture)
		return s2->order - s1->order;
	else return (int) s1->texinfo->texture - (int) s2->texinfo->texture;
}


void GL_BuildBrushSurfaces (void)
{
	model_t	*m;
	brushpolyvert_t *verts = NULL;
	QSCRATCHLOCKER Lock ((void **) &verts, __FUNCTION__);
	D3D10_SUBRESOURCE_DATA srd = {verts, 0, 0};
	int NumBufferVerts = 0;

	// we're going to be using a lot of temp memory here so take a lowmark and free it when done
	int mark = mapPool->GetLowMark ();

	// init for lightmaps
	GL_BeginBuildingLightmaps ();

	for (int j = 1; j < MAX_MODELS; j++)
	{
		if (!(m = cl.model_precache[j])) continue;

		// don't crash on models with 0 surfaces attempting to create a vertex buffer
		if (m->type != mod_brush) continue;
		if (m->name[0] == '*') continue;

		// build lightmaps in texture order for better cache coherence
		qsort (m->surfaces, m->numsurfaces, sizeof (msurface_t), (sortfunc_t) R_SortSurfacesByTexture);

		for (int i = 0; i < m->numsurfaces; i++)
		{
			msurface_t *surf = &m->surfaces[i];

			// link these up for the proper build order
			surf->numindexes = (surf->numedges - 2) * 3;
			surf->firstvert = NumBufferVerts;
			NumBufferVerts += surf->numedges;

			// and now we can create our stuff
			GL_CreateSurfaceLightmap (surf);
			GL_BuildPolygonForSurface (m, surf, &verts[surf->firstvert]);
		}

		// revert the original surface load order for actually drawing
		qsort (m->surfaces, m->numsurfaces, sizeof (msurface_t), (sortfunc_t) R_SortSurfacesByOrder);

		// create and initialize texture chains for this model
		R_CreateTextureChains (m);
	}

	// now we create the vertex buffer
	// release any buffer that may already exist
	SAFE_RELEASE (d3d_SurfaceVertexes);

	// create our new buffer
	D3D10_BUFFER_DESC vbDesc = {
		sizeof (brushpolyvert_t) * NumBufferVerts,
		D3D10_USAGE_IMMUTABLE,
		D3D10_BIND_VERTEX_BUFFER,
		0,
		0
	};

	d3d_Device->CreateBuffer (&vbDesc, &srd, &d3d_SurfaceVertexes);

	// and re-bind it by calling the onmodechange handler
	d3d_SurfHandler.OnModeChange ();

	// and finish up lightmaps
	GL_EndBuildingLightmaps ();

	// because a lot of temp data was used for surface building we now trash it all
	mapPool->FreeToLowMark (mark);
}


